﻿using pk3DS.Core;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Linq;

namespace pk3DS.WinForms;

#region Game Related Classes
public class MapMatrix
{
    public ushort u0;
    public ushort u1;
    public ushort uL;
    public ushort Width, Height;
    private readonly int Area;
    public ushort[] EntryList;
    public Entry[] Entries;
    public Unknown[] Unknowns;

    public byte[] UnkData;

    public MapMatrix(byte[][] data)
    {
        using (var br = new BinaryReader(new MemoryStream(data[0])))
        {
            u0 = br.ReadUInt16();
            u1 = br.ReadUInt16();
            Width = br.ReadUInt16();
            Height = br.ReadUInt16();

            Area = Width * Height;
            Entries = new Entry[Area];
            EntryList = new ushort[Area];
            for (int i = 0; i < Area; i++)
                EntryList[i] = br.ReadUInt16();

            if (br.BaseStream.Position != br.BaseStream.Length)
                uL = br.ReadUInt16();
        }
        if (data.Length > 1)
            ParseUnk(UnkData = data[1]);
    }

    public byte[] Write()
    {
        using var ms = new MemoryStream();
        using var bw = new BinaryWriter(ms);
        bw.Write(u0);
        bw.Write(Width);
        bw.Write(Height);
        foreach (ushort Entry in EntryList)
        {
            bw.Write(Entry);
        }

        bw.Write(uL);
        return ms.ToArray();
    }

    public Bitmap Preview(int Scale, int ColorShift)
    {
        // Require the entries to be defined in order to continue.
        if (Entries.Any(entry => entry == null))
        {
            // Do nothing; images are instead created with the standard dimensions and returned.
        }

        // Fetch Singular Images first
        Bitmap[] EntryImages = new Bitmap[Area];
        for (int i = 0; i < Area; i++)
        {
            EntryImages[i] = Entries[i] == null
                ? new Bitmap(40 * Scale, 40 * Scale)
                : Entries[i].Preview(Scale, ColorShift);
        }

        // Combine all images into one.
        var img = new Bitmap(EntryImages[0].Width * Width, EntryImages[0].Height * Height);

        using var g = Graphics.FromImage(img);
        for (int i = 0; i < Area; i++)
            g.DrawImage(EntryImages[i], new Point(i * EntryImages[0].Width % img.Width, EntryImages[0].Height * (i / Width)));

        return img;
    }

    public class Entry
    {
        public Collision coll;

        public ushort Width, Height;
        private readonly int Area;
        public uint[] Tiles; // Certain bits?

        public Entry(byte[] data)
        {
            using var br = new BinaryReader(new MemoryStream(data));
            Width = br.ReadUInt16();
            Height = br.ReadUInt16();
            Area = Width * Height;
            Tiles = new uint[Area];
            for (int i = 0; i < Area; i++)
            {
                Tiles[i] = br.ReadUInt32();
            }
        }

        public byte[] Write()
        {
            using var ms = new MemoryStream();
            using var bw = new BinaryWriter(ms);
            bw.Write(Width);
            bw.Write(Height);
            foreach (uint Tile in Tiles)
            {
                bw.Write(Tile);
            }

            return ms.ToArray();
        }

        public Bitmap Preview(int s, int ColorShift)
        {
            byte[] bmpData = BytePreview(s, ColorShift);
            var b = new Bitmap(Width * s, Height * s, PixelFormat.Format32bppArgb);
            var bData = b.LockBits(new Rectangle(0, 0, Width * s, Height * s), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
            System.Runtime.InteropServices.Marshal.Copy(bmpData, 0, bData.Scan0, bmpData.Length);
            b.UnlockBits(bData);

            return b;
        }

        public byte[] BytePreview(int s, int ColorShift)
        {
            byte[] bmpData = new byte[4 * Width * Height * s * s];
            for (int i = 0; i < Area; i++)
            {
                int X = i % 40;
                int Y = i / 40;
                uint colorValue = Tiles[i] == 0x01000021
                    ? 0xFF000000
                    : LCRNG32.Advance(Tiles[i], ColorShift) | 0xFF000000;

                byte[] pixel = BitConverter.GetBytes(colorValue);
                for (int x = 0; x < s * s; x++)
                {
                    pixel.CopyTo(bmpData, 4 * ((((Y * s) + (x / s)) * Width * s) + (X * s) + (x % s)));
                }
            }
            return bmpData;
        }
    }

    public class Collision
    {
        public string Magic;
        public int termOffset;
        public int U5D8;
        public byte[] UnknownBytes;
        public CollisionObject[] Map40;
        public int[] MapInts;
        public CollisionObject[] MapMisc;
        public string termMagic;
        public byte[] termData;

        public Collision(byte[] data)
        {
            using var br = new BinaryReader(new MemoryStream(data));
            Magic = new string(br.ReadChars(4)); // Magic
            if (Magic != "coll")
            {
                return; // all other properties are null
            }

            termOffset = br.ReadInt32();
            U5D8 = br.ReadInt32();
            UnknownBytes = br.ReadBytes(0x14);

            // Read 40 collision rectangles
            Map40 = new CollisionObject[40];
            for (int i = 0; i < Map40.Length; i++)
            {
                Map40[i] = new CollisionObject(br.ReadBytes(0x10));
            }

            // Read 32 Int32s
            MapInts = new int[0x20];
            for (int i = 0; i < MapInts.Length; i++)
            {
                MapInts[i] = br.ReadInt32();
            }

            // Read misc collision rectangles
            int ct = termOffset - (int)br.BaseStream.Position + 0x10;
            MapMisc = new CollisionObject[ct / 0x10];
            for (int i = 0; i < MapMisc.Length; i++)
            {
                MapMisc[i] = new CollisionObject(br.ReadBytes(0x10));
            }

            // Read Term
            termMagic = new string(br.ReadChars(4));
            if (termMagic != "term")
            {
                return; // all other properties are null
            }

            // Read the rest of the data....
            termData = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position));
        }

        public class CollisionObject(byte[] data)
        {
            private readonly float _0 = BitConverter.ToSingle(data, 0x0);
            private readonly float _1 = BitConverter.ToSingle(data, 0x4);
            private readonly float _2 = BitConverter.ToSingle(data, 0x8);
            private readonly float _3 = BitConverter.ToSingle(data, 0xC); // rarely used

            // I don't even know...
            public float F1 => _0 / 2;
            public float F2 => _1 * 80;
            public float F3 => _2 / 2;
            public float F4 => _3 / 1;

            public override string ToString()
            {
                return string.Join(", ",
                    F1.ToString(CultureInfo.InvariantCulture),
                    F2.ToString(CultureInfo.InvariantCulture),
                    F3.ToString(CultureInfo.InvariantCulture),
                    F4.ToString(CultureInfo.InvariantCulture));
            }
        }
    }

    public string Unk2String()
    {
        return Unknowns.Aggregate("", (current, l) => current + $"{l.Direction}: {l.P1,3} {l.P2,3} {l.P3,3} {l.P4,3}{Environment.NewLine,3}");
    }

    private void ParseUnk(byte[] data)
    {
        List<Unknown> unk = [];
        using var ms = new MemoryStream(data);
        using var br = new BinaryReader(ms);
        do
        {
            var obj = new Unknown
            {
                Direction = br.ReadUInt32(),
                _1 = br.ReadSingle(),
                _2 = br.ReadSingle(),
                _3 = br.ReadSingle(),
                _4 = br.ReadSingle(),
            };
            unk.Add(obj);
        } while (unk[^1].Direction != 0);

        unk.RemoveAt(unk.Count - 1);
        Unknowns = [.. unk];
    }

    public class Unknown
    {
        public uint Direction;
        public float _1;
        public float _2;
        public float _3;
        public float _4;

        public int P1 => (int)_1 / 18;
        public int P2 => (int)_2 / 18;
        public int P3 => (int)_3 / 18;
        public int P4 => (int)_4 / 18;
    }
}

public class ZoneData
{
    internal const int Size = 0x38;
    public readonly byte[] Data;

    public byte MapType { get => Data[0]; set => Data[0] = value; }
    public byte MapMove { get => Data[1]; set => Data[1] = value; }
    public int MapArea { get => BitConverter.ToUInt16(Data, 0x02); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x02); }
    public int MapMatrix { get => BitConverter.ToUInt16(Data, 0x04); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x04); }
    public int TextFile { get => BitConverter.ToUInt16(Data, 0x06); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x06); }

    public uint BGMSpring { get => BitConverter.ToUInt32(Data, 0x08); set => BitConverter.GetBytes(value).CopyTo(Data, 0x08); }
    public uint BGMSummer { get => BitConverter.ToUInt32(Data, 0x0C); set => BitConverter.GetBytes(value).CopyTo(Data, 0x0C); }
    public uint BGMAutumn { get => BitConverter.ToUInt32(Data, 0x10); set => BitConverter.GetBytes(value).CopyTo(Data, 0x10); }
    public uint BGMWinter { get => BitConverter.ToUInt32(Data, 0x14); set => BitConverter.GetBytes(value).CopyTo(Data, 0x14); }

    public int ScriptFile { get => BitConverter.ToUInt16(Data, 0x18); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x18); }
    public int TownMapGroup { get => BitConverter.ToUInt16(Data, 0x1A); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x1A); }

    public int ParentMap // 0x1C - low 10 bits
    {
        get => BitConverter.ToUInt16(Data, 0x1C) & 0x3FF;
        set => BitConverter.GetBytes((ushort)(value | (BitConverter.ToUInt16(Data, 0x1C) & ~0x3FF))).CopyTo(Data, 0x1C);
    }

    public int OLValue // 0x1C - high 6 bits
    {
        get => BitConverter.ToUInt16(Data, 0x1C) >> 10;
        set => BitConverter.GetBytes((ushort)((value << 10) | ParentMap)).CopyTo(Data, 0x1C);
    }

    // 5,1,1,7
    public int Weather // 0x1E, low 5
    {
        get => BitConverter.ToUInt16(Data, 0x1E) & 0x1F;
        set => BitConverter.GetBytes((ushort)(value | (BitConverter.ToUInt16(Data, 0x1E) & ~0x1F))).CopyTo(Data, 0x1E);
    }

    public bool IsSkyBoxEnable { get => ((Data[0x1E] >> 5) & 1) == 1; set => Data[0x1E] = (byte)((Data[0x1E] & ~0x20) | (value ? 1 << 5 : 0)); }
    public bool IsRollerSkateEnable { get => ((Data[0x1E] >> 6) & 1) == 1; set => Data[0x1E] = (byte)((Data[0x1E] & ~0x40) | (value ? 1 << 6 : 0)); }

    public int BattleBackground // 7-13
    {
        get => (BitConverter.ToUInt16(Data, 0x1E) >> 7) & 0x7F;
        set => BitConverter.GetBytes((ushort)(((value & 0x7F) << 7) | (BitConverter.ToUInt16(Data, 0x1E) & ~0x3F80))).CopyTo(Data, 0x1E);
    }

    private uint Unk_20 { get => BitConverter.ToUInt32(Data, 0x20); set => BitConverter.GetBytes(value).CopyTo(Data, 0x20); }
    public uint MapChange { get => Unk_20 & 0x1Fu; set => Unk_20 = (Unk_20 & ~0x1Fu) | value; }
    // ??? 5
    public bool IsBicycleEnable { get => ((Unk_20 >> 10) & 1) == 1; set => Unk_20 = (Unk_20 & ~(1u << 10)) | (value ? 1u << 10 : 0); }
    public bool IsRunEnable { get => ((Unk_20 >> 11) & 1) == 1; set => Unk_20 = (Unk_20 & ~(1u << 11)) | (value ? 1u << 11 : 0); }
    public bool IsEscapeRopeEnable { get => ((Unk_20 >> 12) & 1) == 1; set => Unk_20 = (Unk_20 & ~(1u << 12)) | (value ? 1u << 12 : 0); }
    public bool IsFlyEnable { get => ((Unk_20 >> 13) & 1) == 1; set => Unk_20 = (Unk_20 & ~(1u << 13)) | (value ? 1u << 13 : 0); }
    public bool IsBGM { get => ((Unk_20 >> 14) & 1) == 1; set => Unk_20 = (Unk_20 & ~(1u << 14)) | (value ? 1u << 14 : 0); }
    public bool IsUnk { get => ((Unk_20 >> 15) & 1) == 1; set => Unk_20 = (Unk_20 & ~(1u << 15)) | (value ? 1u << 15 : 0); }

    // unused - camera
    public ushort Camera1 { get => BitConverter.ToUInt16(Data, 0x22); set => BitConverter.GetBytes(value).CopyTo(Data, 0x22); }

    public ushort Camera2 { get => BitConverter.ToUInt16(Data, 0x24); set => BitConverter.GetBytes(value).CopyTo(Data, 0x24); }
    public uint CameraFlags { get => BitConverter.ToUInt32(Data, 0x26); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x26); }

    private short X { get => BitConverter.ToInt16(Data, 0x2C); set => BitConverter.GetBytes(value).CopyTo(Data, 0x2C); }
    public short Z { get => BitConverter.ToInt16(Data, 0x2E); set => BitConverter.GetBytes(value).CopyTo(Data, 0x2E); }
    private short Y { get => BitConverter.ToInt16(Data, 0x30); set => BitConverter.GetBytes(value).CopyTo(Data, 0x30); }
    private short X2 { get => BitConverter.ToInt16(Data, 0x32); set => BitConverter.GetBytes(value).CopyTo(Data, 0x32); }
    public short Z2 { get => BitConverter.ToInt16(Data, 0x34); set => BitConverter.GetBytes(value).CopyTo(Data, 0x34); }
    private short Y2 { get => BitConverter.ToInt16(Data, 0x36); set => BitConverter.GetBytes(value).CopyTo(Data, 0x36); }

    public float PX { get => (float)X / 18; set => X = (short)(18 * value); }
    public float PY { get => (float)Y / 18; set => Y = (short)(18 * value); }
    public float PX2 { get => (float)X2 / 18; set => X2 = (short)(18 * value); }
    public float PY2 { get => (float)Y2 / 18; set => Y2 = (short)(18 * value); }

    public ZoneData(byte[] data)
    {
        if (data.Length != Size)
        {
            return;
        }

        Data = data;
    }

    public byte[] Write()
    {
        return Data;
    }
}

public class Zone
{
    public readonly ZoneData ZD;
    public ZoneEntities Entities;
    public readonly ZoneScript MapScript;
    public readonly ZoneEncounters Encounters;
    public readonly ZoneUnknown File5;

    public Zone(byte[][] Zone)
    {
        // A ZO is comprised of 4-5 files.

        // Array 0 is [Map Info]
        ZD = new ZoneData(Zone[0]);
        // Array 1 is [Overworld Entities & their Scripts]
        Entities = new ZoneEntities(Zone[1]);
        // Array 2 is [Map Script]
        MapScript = new ZoneScript(Zone[2]);
        // Array 3 is [Wild Encounters]
        Encounters = new ZoneEncounters(Zone[3]);
        // Array 4 is [???] - May not be present in all.
        if (Zone.Length <= 4)
        {
            return;
        }

        File5 = new ZoneUnknown(Zone[4]);
    }

    public byte[][] Write()
    {
        byte[][] Zone = new byte[File5 != null ? 5 : 4][];
        Zone[0] = ZD.Data;
        Zone[1] = Entities.Write();
        Zone[2] = MapScript.Write();
        Zone[3] = Encounters.Write();
        if (Zone.Length <= 4)
        {
            return Zone;
        }

        Zone[4] = File5.Write();
        return Zone;
    }

    public class ZoneEntities
    {
        public byte[] Data;

        public int Length;
        public int FurnitureCount, NPCCount, WarpCount, TriggerCount, UnknownCount;
        public EntityFurniture[] Furniture;
        public EntityNPC[] NPCs;
        public EntityWarp[] Warps;
        public EntityTrigger1[] Triggers1;
        public EntityTrigger2[] Triggers2;

        public Script Script;

        public ZoneEntities(byte[] data)
        {
            Data = data;

            using var br = new BinaryReader(new MemoryStream(data));
            // Load Header
            Length = br.ReadInt32();
            Furniture = new EntityFurniture[FurnitureCount = br.ReadByte()];
            NPCs = new EntityNPC[NPCCount = br.ReadByte()];
            Warps = new EntityWarp[WarpCount = br.ReadByte()];
            Triggers1 = new EntityTrigger1[TriggerCount = br.ReadByte()];
            Triggers2 = new EntityTrigger2[UnknownCount = br.ReadInt32()]; // not sure if there's other types or if the remaining 3 bytes are padding.

            // Load Entitites
            for (int i = 0; i < FurnitureCount; i++)
            {
                Furniture[i] = new EntityFurniture(br.ReadBytes(EntityFurniture.Size));
            }

            for (int i = 0; i < NPCCount; i++)
            {
                NPCs[i] = new EntityNPC(br.ReadBytes(EntityNPC.Size));
            }

            for (int i = 0; i < WarpCount; i++)
            {
                Warps[i] = new EntityWarp(br.ReadBytes(EntityWarp.Size));
            }

            for (int i = 0; i < TriggerCount; i++)
            {
                Triggers1[i] = new EntityTrigger1(br.ReadBytes(EntityTrigger1.Size));
            }

            for (int i = 0; i < UnknownCount; i++)
            {
                Triggers2[i] = new EntityTrigger2(br.ReadBytes(EntityTrigger2.Size));
            }

            // Load Script Data
            int len = br.ReadInt32();
            br.BaseStream.Position -= 4;
            Script = new Script(br.ReadBytes(len));
        }

        public byte[] Write()
        {
            byte[] F = new byte[Furniture.Length * EntityFurniture.Size];
            for (int i = 0; i < Furniture.Length; i++)
            {
                Furniture[i].Write().CopyTo(F, i * EntityFurniture.Size);
            }

            byte[] N = new byte[NPCs.Length * EntityNPC.Size];
            for (int i = 0; i < NPCs.Length; i++)
            {
                NPCs[i].Write().CopyTo(N, i * EntityNPC.Size);
            }

            byte[] W = new byte[Warps.Length * EntityWarp.Size];
            for (int i = 0; i < Warps.Length; i++)
            {
                Warps[i].Write().CopyTo(W, i * EntityWarp.Size);
            }

            byte[] T = new byte[Triggers1.Length * EntityTrigger1.Size];
            for (int i = 0; i < Triggers1.Length; i++)
            {
                Triggers1[i].Write().CopyTo(T, i * EntityTrigger1.Size);
            }

            byte[] U = new byte[Triggers2.Length * EntityTrigger2.Size];
            for (int i = 0; i < Triggers2.Length; i++)
            {
                Triggers2[i].Write().CopyTo(U, i * EntityTrigger2.Size);
            }

            // Assemble entity information
            byte[] OWEntities = [.. F, .. N, .. W, .. T, .. U];
            byte[] EntityLength = BitConverter.GetBytes(8 + OWEntities.Length);
            byte[] EntityCounts = [(byte)Furniture.Length, (byte)NPCs.Length, (byte)Warps.Length, (byte)Triggers1.Length, (byte)Triggers2.Length, 0, 0, 0,
            ];

            // Reassemble NPC portion
            byte[] OWEntityData = [.. EntityLength, .. EntityCounts, .. OWEntities];

            // Reassemble Script portion
            byte[] OWScriptData = Script.Write();

            byte[] finalData = [.. OWEntityData, .. OWScriptData];

            // Add padding zeroes if required (yield size % 4 == 0)
            if (finalData.Length % 4 != 0)
            {
                Array.Resize(ref finalData, finalData.Length + 4 - (finalData.Length % 4));
            }

            return finalData;
        }

        // Entity Classes
        public class EntityFurniture
        {
            // Usable Attributes
            public int Script { get => BitConverter.ToUInt16(Raw, 0x00); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x00); }

            public int U2 { get => BitConverter.ToUInt16(Raw, 0x02); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x02); }
            public int U4 { get => BitConverter.ToUInt16(Raw, 0x04); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x04); }
            public int U6 { get => BitConverter.ToUInt16(Raw, 0x06); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x06); }

            // Coordinates have some upper-bit usage it seems...
            public int X { get => BitConverter.ToUInt16(Raw, 0x08); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x08); }
            public int Y { get => BitConverter.ToUInt16(Raw, 0x0A); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x0A); }
            // Next two bytes should be dealing with furniture width?
            public int WX { get => BitConverter.ToInt16(Raw, 0x0C); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x0C); }
            public int WY { get => BitConverter.ToInt16(Raw, 0x0E); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x0E); }

            public int U10 { get => BitConverter.ToInt32(Raw, 0x10); set => BitConverter.GetBytes(value).CopyTo(Raw, 0x10); }

            public byte[] Raw;
            public byte[] OriginalData;
            internal const byte Size = 0x14;

            public EntityFurniture(byte[] data = null)
            {
                Raw = data ?? new byte[Size];
                OriginalData = (byte[])Raw.Clone();
            }

            public byte[] Write()
            {
                return Raw;
            }
        }

        public class EntityNPC
        {
            // Usable Attributes
            public int ID { get => BitConverter.ToUInt16(Raw, 0x00); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x00); }
            public int Model { get => BitConverter.ToUInt16(Raw, 0x02); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x02); }
            public int MovePermissions { get => BitConverter.ToUInt16(Raw, 0x04); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x04); }
            public int MovePermissions2 { get => BitConverter.ToUInt16(Raw, 0x06); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x06); }
            public int SpawnFlag { get => BitConverter.ToUInt16(Raw, 0x08); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x08); }
            public int Script { get => BitConverter.ToUInt16(Raw, 0x0A); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x0A); }
            public int FaceDirection { get => BitConverter.ToUInt16(Raw, 0x0C); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x0C); }
            public int SightRange { get => BitConverter.ToUInt16(Raw, 0x0E); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x0E); }

            // XY Only
            public int U10 { get => BitConverter.ToUInt16(Raw, 0x10); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x10); }
            public int U12 { get => BitConverter.ToUInt16(Raw, 0x12); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x12); }

            // Shorts
            public int U14 { get => BitConverter.ToInt16(Raw, 0x14); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x14); }
            public int U16 { get => BitConverter.ToInt16(Raw, 0x16); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x16); }
            // Negative only in X/Y... seeing behind them? Might be projection of an interaction area.
            public int U18 { get => BitConverter.ToInt16(Raw, 0x18); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x18); }
            public int U1A { get => BitConverter.ToInt16(Raw, 0x1A); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x1A); }

            // WalkArea Leashes (?): If these are for NPCs that walk in an area, I'm not sure if there's a direction specified.
            // Set L# to -1 to turn off.
            public int L1 { get => BitConverter.ToInt16(Raw, 0x1C); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x1C); }
            public int L2 { get => BitConverter.ToInt16(Raw, 0x1E); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x1E); }
            public int L3 { get => BitConverter.ToInt16(Raw, 0x20); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x20); }
            // Leash Direction? Only used when an area is specified.
            public int LDir { get => BitConverter.ToUInt16(Raw, 0x22); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x22); }

            // 0x24-0x25 is Unused in OR/AS, rarely 1 in XY
            public int U24 { get => BitConverter.ToUInt16(Raw, 0x24); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x24); }
            // 0x26-0x27 is Unused in OR/AS

            // Highest bits for X/Y seem to be fractions of a coordinate?
            public int X { get => BitConverter.ToUInt16(Raw, 0x28); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x28); }
            public int Y { get => BitConverter.ToUInt16(Raw, 0x2A); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x2A); }

            // -360, 360 ????
            public float Degrees { get => BitConverter.ToSingle(Raw, 0x2C); set => BitConverter.GetBytes(value).CopyTo(Raw, 0x2C); }
            public float Deg18 => Degrees / 18;

            public byte[] Raw;
            public byte[] OriginalData;
            internal const byte Size = 0x30;

            public EntityNPC(byte[] data = null)
            {
                Raw = data ?? new byte[Size];
                OriginalData = (byte[])Raw.Clone();
            }

            public byte[] Write()
            {
                return Raw;
            }
        }

        public class EntityWarp
        {
            // Usable Attributes
            public int DestinationMap { get => BitConverter.ToUInt16(Raw, 0x00); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x00); }
            public int DestinationTileIndex { get => BitConverter.ToUInt16(Raw, 0x02); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x02); }

            // Not sure if these are widths or face direction
            public byte FaceDirection { get => Raw[0x04]; set => Raw[0x4] = value; }
            public byte SceneTransitionEffect { get => Raw[0x05]; set => Raw[0x5] = value; }

            // Either 0 or 1, only in X/Y
            // 0: use x,z,y
            // 1: use x,y,{unused}
            public ushort CoordinateType { get => BitConverter.ToUInt16(Raw, 0x06); set => BitConverter.GetBytes(value).CopyTo(Raw, 0x06); }
            // Coordinates have some upper-bit usage it seems...
            public short X { get => BitConverter.ToInt16(Raw, 0x08); set => BitConverter.GetBytes(value).CopyTo(Raw, 0x08); }
            public short Z { get => BitConverter.ToInt16(Raw, 0x0A); set => BitConverter.GetBytes(value).CopyTo(Raw, 0x0A); }
            public short Y { get => BitConverter.ToInt16(Raw, 0x0C); set => BitConverter.GetBytes(value).CopyTo(Raw, 0x0C); }

            public decimal PX => (decimal)X / 18;
            public decimal PY => (decimal)Y / 18;

            // Stretches RIGHT
            public short Width { get => BitConverter.ToInt16(Raw, 0x0E); set => BitConverter.GetBytes(value).CopyTo(Raw, 0x0E); }
            // Stretches DOWN
            public short Height { get => BitConverter.ToInt16(Raw, 0x10); set => BitConverter.GetBytes(value).CopyTo(Raw, 0x10); }
            // 0-bidirectional
            // 1-send only
            // 2-receive only
            public ushort Directionality { get => BitConverter.ToUInt16(Raw, 0x12); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x12); }

            // 0x14-0x15 Unused
            // 0x16-0x17 Unused

            public byte[] Raw;
            public byte[] OriginalData;
            internal const byte Size = 0x18;

            public EntityWarp(byte[] data = null)
            {
                Raw = data ?? new byte[Size];
                OriginalData = (byte[])Raw.Clone();
            }

            public byte[] Write()
            {
                return Raw;
            }
        }

        public class EntityTrigger1
        {
            // Usable Attributes
            public int Script { get => BitConverter.ToUInt16(Raw, 0x00); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x00); }
            public int U2 { get => BitConverter.ToUInt16(Raw, 0x02); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x02); }
            public int Constant { get => BitConverter.ToUInt16(Raw, 0x04); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x04); }

            // 0 or 1 for type2, 0/5-8 for type1
            public int U6 { get => BitConverter.ToUInt16(Raw, 0x06); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x06); }
            // 0 or 1, always 0 in ORAS
            public int U8 { get => BitConverter.ToUInt16(Raw, 0x08); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x08); }

            // 0x0A-0x0B unused

            public int X { get => BitConverter.ToUInt16(Raw, 0x0C); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x0C); }
            public int Y { get => BitConverter.ToUInt16(Raw, 0x0E); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x0E); }

            public int Width { get => BitConverter.ToInt16(Raw, 0x10); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x10); }
            public int Height { get => BitConverter.ToInt16(Raw, 0x12); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x12); }
            public int U14 { get => BitConverter.ToInt16(Raw, 0x14); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x14); }
            public int U16 { get => BitConverter.ToInt16(Raw, 0x16); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x16); }

            public byte[] Raw;
            public byte[] OriginalData;
            internal const byte Size = 0x18;

            public EntityTrigger1(byte[] data = null)
            {
                Raw = data ?? new byte[Size];
                OriginalData = (byte[])Raw.Clone();
            }

            public byte[] Write()
            {
                return Raw;
            }
        }

        public class EntityTrigger2
        {
            // Usable Attributes
            public int Script { get => BitConverter.ToUInt16(Raw, 0x00); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x00); }
            public int U2 { get => BitConverter.ToUInt16(Raw, 0x02); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x02); }
            public int Constant { get => BitConverter.ToUInt16(Raw, 0x04); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x04); }

            // 0 or 1 for type2, 0/5-8 for type1
            public int U6 { get => BitConverter.ToUInt16(Raw, 0x06); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x06); }
            // 0 or 1, always 0 in ORAS
            public int U8 { get => BitConverter.ToUInt16(Raw, 0x08); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x08); }

            // 0x0A-0x0B unused

            public int X { get => BitConverter.ToUInt16(Raw, 0x0C); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x0C); }
            public int Y { get => BitConverter.ToUInt16(Raw, 0x0E); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x0E); }

            public int Width { get => BitConverter.ToInt16(Raw, 0x10); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x10); }
            public int Height { get => BitConverter.ToInt16(Raw, 0x12); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x12); }
            public int U14 { get => BitConverter.ToInt16(Raw, 0x14); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x14); }
            public int U16 { get => BitConverter.ToInt16(Raw, 0x16); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x16); }

            public byte[] Raw;
            public byte[] OriginalData;
            internal const byte Size = 0x18;

            public EntityTrigger2(byte[] data = null)
            {
                Raw = data ?? new byte[Size];
                OriginalData = (byte[])Raw.Clone();
            }

            public byte[] Write() => Raw;
        }
    }

    public class ZoneScript(byte[] data)
    {
        public byte[] Data = data; // File details unknown.
        public Script Script = new(data);

        public byte[] Write()
        {
            Data = Script.Write();
            return Data;
        }
    }

    public class ZoneEncounters
    {
        public byte[] Data; // File details unknown.
        public byte[] Header;
        public EncounterSet[] Encounters;

        public ZoneEncounters(byte[] data)
        {
            Data = data;

            using var br = new BinaryReader(new MemoryStream(data));
            Header = br.ReadBytes(0x10);
            Encounters = new EncounterSet[(int)(br.BaseStream.Length - br.BaseStream.Position) / 4];
            for (int i = 0; i < Encounters.Length; i++)
            {
                Encounters[i] = new EncounterSet(br.ReadBytes(4));
            }
        }

        public byte[] Write()
        {
            byte[] data = Header; // Start with the header data, then concat every encounter in afterwards.
            return Encounters.Aggregate(data, (current, t) => [.. current, .. t.Write()]);
        }

        public class EncounterSet
        {
            public int Species;
            public int Form;
            public byte LevelMin, LevelMax;

            public EncounterSet(byte[] data)
            {
                using var br = new BinaryReader(new MemoryStream(data));
                ushort SpecForm = br.ReadUInt16();
                Species = SpecForm & 0x7FF;
                Form = SpecForm >> 11;
                LevelMin = br.ReadByte();
                LevelMax = br.ReadByte();
            }

            public byte[] Write()
            {
                using var ms = new MemoryStream();
                using var bw = new BinaryWriter(ms);
                bw.Write((ushort)(Species | (Form << 11)));
                bw.Write(LevelMin);
                bw.Write(LevelMax);
                return ms.ToArray();
            }
        }
    }

    public class ZoneUnknown(byte[] data)
    {
        public byte[] FileData = data; // File details unknown.

        public byte[] Write()
        {
            return FileData;
        }
    }
}

#endregion